package com.shengtsource.mvc.servlet;

import com.shengtsource.mvc.anno.*;
import com.shengtsource.mvc.pojo.Handler;
import org.apache.commons.lang3.StringUtils;


import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 调度Servlet
 * @author shengtsource
 */
public class DispatcherServlet extends HttpServlet {

    /**
     * 对象集合
     */
    private Map<String,Object> objectMap = new HashMap<>();

    /**
     * 类全限定名集合
     */
    private List<String> classNames = new ArrayList<>();

    /**
     * Handler映射集合
     */
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init() throws ServletException {
        //1.加载配置文件，获取scanPackage
        String contextConfigLocation = this.getInitParameter("contextConfigLocation");
        InputStream in = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        Properties properties = doScanConfig(in);

        //2.扫描指定包，获取所有类全限定名集合
        String scanPackage = (String) properties.get("scanPackage");
        doScanPackage(scanPackage);

        //3.对象实例化
        doInstance(classNames);

        //4.对象依赖注入
        doAutoWired();

        //5.循环遍历对象及对象中的方法，建立URL和Handler方法的映射关系
        initHandlerMapping();

    }

    private void initHandlerMapping() {
        try{
            for(Map.Entry<String,Object> entry : objectMap.entrySet()) {

                Object obj = entry.getValue();
                Class clazz = obj.getClass();

                //类上没有标注Controller注解，不处理
                if(!clazz.isAnnotationPresent(Controller.class)) {
                    continue;
                }

                //处理RequestMapping注解
                //URL前缀
                String urlPrefix = "";
                if(clazz.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping requestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
                    urlPrefix = requestMapping.value();
                }


                //处理Security注解
                //可以访问当前handler方法集合
                List<String> handleUser = new ArrayList<>();
                if(clazz.isAnnotationPresent(Security.class)) {
                    Security security = (Security) clazz.getAnnotation(Security.class);
                    String[] value = security.value();
                    handleUser = Arrays.asList(value);
                }

                //遍历所有方法，解析Handler
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if(!method.isAnnotationPresent(RequestMapping.class)) {continue;}
                    RequestMapping methodMapping = method.getAnnotation(RequestMapping.class);
                    String url = methodMapping.value();
                    // /demo/query
                    url = urlPrefix.concat(url);

                    //构造handler对象
                    Handler handler = new Handler(Pattern.compile(url),method,obj);
                    
                    //计算参数位置信息
                    Parameter[] parameters = method.getParameters();
                    for (int i = 0; i < parameters.length; i++) {
                        if(parameters[i].getType() == HttpServletRequest.class
                                || parameters[i].getType() == HttpServletResponse.class) {
                            handler.getParameterIndexMapping().put(parameters[i].getType().getSimpleName(),i);
                        }else {
                            handler.getParameterIndexMapping().put(parameters[i].getName(),i);
                        }
                    }

                    //处理Security
                    if(method.isAnnotationPresent(Security.class)) {
                        Security security = method.getAnnotation(Security.class);
                        String[] value = security.value();
                        List<String> methodHandleUser = new ArrayList<>(handleUser);
                        methodHandleUser.addAll(Arrays.asList(value));
                        handler.setPermissionUser(methodHandleUser);
                    }

                    handlerMapping.add(handler);
                }

            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 依赖注入
     */
    private void doAutoWired()  {
        try {
            //遍历所有对象
            for (Map.Entry<String, Object> entry : objectMap.entrySet()) {
                String beanName = entry.getKey();
                Object obj = entry.getValue();
                Class clazz = obj.getClass();
                //获取对象所有属性
                Field[] declaredFields = clazz.getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    if (!declaredField.isAnnotationPresent(Autowired.class)) {
                        continue;
                    }
                    //设置属性暴力访问
                    declaredField.setAccessible(true);

                    Autowired autowired = declaredField.getAnnotation(Autowired.class);
                    //beanName为空，则根据type注入
                    String value = autowired.value();
                    value = value == null || "".equals(value.trim())
                            ? declaredField.getType().getName() : value;
                    declaredField.set(obj, objectMap.get(value));
                }
                objectMap.put(beanName, obj);
            }
        }catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 对象实例化
     * @param classNameList
     */
    private void doInstance(List<String> classNameList) {
        String beanName = null;
        Object obj = null;
        try{
            for (String className : classNameList) {
                //获取Class对象
                Class clazz = Class.forName(className);

                if(clazz.isAnnotationPresent(Controller.class)) {
                    Controller controller = (Controller) clazz.getAnnotation(Controller.class);
                    beanName = controller.value();
                    beanName = beanName == null || "".equals(beanName.trim())
                            ? getFirstCharUpperStr(clazz.getSimpleName()) : beanName;
                    obj = clazz.newInstance();
                    objectMap.put(beanName,obj);
                    continue;
                }else if(clazz.isAnnotationPresent(Service.class)) {
                    Service service = (Service) clazz.getAnnotation(Service.class);
                    beanName = service.value();
                    beanName = beanName == null || "".equals(beanName.trim())
                            ? getFirstCharUpperStr(clazz.getSimpleName()) : beanName;
                    obj = clazz.newInstance();
                    objectMap.put(beanName,obj);

                    //适配以接口注入的形式，以接口名为key存进去
                    Class[] interfaces = clazz.getInterfaces();
                    for (Class instance : interfaces) {
                        objectMap.put(instance.getName(),obj);
                    }
                    continue;
                }else {
                    continue;
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 获取首字符小写的字符串
     * @param str
     * @return
     */
    private String getFirstCharUpperStr(String str) {
        //首字符小写类名
        String firstChar = String.valueOf(str.charAt(0));
        str = str.replaceFirst(firstChar,firstChar.toLowerCase(Locale.ENGLISH));
        return str;
    }

    /**
     * 扫描指定包路径下类的全限定名集合
     * @param scanPackage
     * @return
     */
    private void doScanPackage(String scanPackage) {

        //获取当前类所在工程路径
        String basePath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        //替换包名为路径,把com.shengtsource.demo 替换com/shengtsource/demo
        String scanPackagePath = scanPackage.replace(".", File.separator);
        //扫描包的磁盘物理路径
        String packagePath = basePath.concat(File.separator).concat(scanPackagePath);

        File packFile = new File(packagePath);
        //获取目录下所有的文件
        File[] files = packFile.listFiles();
        for (File file : files) {
            //如果是文件夹
            if(file.isDirectory()) {
                //递归调用
                doScanPackage(scanPackage.concat(".").concat(file.getName()));
            }else {
                //如果是文件，根据文件名获取类的全限定名
                String className = scanPackage.concat(".").concat(file.getName()).replace(".class","");
                classNames.add(className);
            }
        }
    }

    /**
     * 扫描配置文件，获取配置信息
     * @param in 配置文件输入流
     * @return
     */
    private Properties doScanConfig(InputStream in) {
        Properties properties = new Properties();
        try{
            properties.load(in);
        }catch (IOException e) {
            e.printStackTrace();
        }
        return properties;
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.获取URL /demo/query
        String uri = req.getRequestURI();
        //2.根据URL找到对应的handler
        Handler handler = getHandler(uri);
        if(handler == null) {
            resp.getWriter().write("404 not found");
            return;
        }

        //判断当前用户是否有权限访问
        String username = req.getParameter("username");
        List<String> permissionUser = handler.getPermissionUser();
        if(null != permissionUser && permissionUser.size() > 0
                && !permissionUser.contains(username)) {
            resp.getWriter().write("username: " + username +" has no permission for url:" + uri);
            return;
        }

        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        //3.参数绑定
        Object[] params = new Object[parameterTypes.length];
        Map<String, String[]> parameterMap = req.getParameterMap();
        for(Map.Entry<String,String[]> entry :parameterMap.entrySet()) {
            //[1,2] "1,2"
            String value = StringUtils.join(entry.getValue(), ",");
            if(handler.getParameterIndexMapping().containsKey(entry.getKey())) {
                //拿到参数的索引位置，赋值给参数集合
                Integer index = handler.getParameterIndexMapping().get(entry.getKey());
                params[index] = value;
            }

        }

        //处理request对象和response对象
        Integer reqIndex = handler.getParameterIndexMapping().get(HttpServletRequest.class.getSimpleName());
        Integer respIndex = handler.getParameterIndexMapping().get(HttpServletResponse.class.getSimpleName());
        params[reqIndex] = req;
        params[respIndex] = resp;

        //4.调用handler
        try {
            handler.getMethod().invoke(handler.getTargetObject(),params);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        resp.getWriter().write(username +" access success,url" +
                ": "+ uri);
    }

    /**
     * 获取Handler
     * @param uri
     * @return
     */
    private Handler getHandler(String uri) {
        if(handlerMapping.isEmpty()){return null;}
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(uri);
            if(matcher.matches()) {
                return handler;
            }
        }
        return null;
    }
}
